var pilotGraphDebug = false;
var chartQueue = [];  // page context scope???
var highCharts;  // Page context scope????
var loading = false;
var lastLoadingCount = 0;
var repeatedX = 0;
var _mouseWatchDogTimer;
var messageCount = 0;
var clearOn = false;
var processConfigOn = false;
var MAX_RECONNECT_TIMEOUT = 30000;
var historicalStartTime = 0;
var totalBytes = 0;
var graphInfo = {};
var disconnected = isValued( getParameterByName( "disconnected" ) ) && getParameterByName( "disconnected" ) != "";
var lastHearbeatTime = 0;
var HEARTBEAT_INTERVAL = 30000;
var MAX_MISSED_HEARTBEAT = 3;

var logger = console;
function getConfig() {
  if (graphInfo && graphInfo.graphConfig) {
    return graphInfo.graphConfig;
  }    
  return null;
}

function getConfigParameter( key ) {
  var config = getConfig();
  if ( config && config.parameters ) {
    return config.parameters[ key ];
  }
  return null;
}

function redraw() {
  var index = 0;
  if (chartQueue.length > 0) {
    var seriesPointCache = {};
    for (index = 0; index < chartQueue.length && index < 20000; index++) {
      var payload = chartQueue[index];
      if (!isValued(payload)) {
        continue;
      }
      if ( payload.type && payload.type =="command" ) {
        handleCommands(payload);
      } else {
        if ( !payload.zoomStart ) {
          handleSeriesData(seriesPointCache, payload);
        } else {
          highCharts.processZoomedData( payload );
        }
        
      }
    }
    
    if (clearOn) {
      return;
    }

    //dont need to add one, already added from last ++
    chartQueue.splice(0, index);
    if (index > 0 && chartQueue.length == 0 && !loading && highCharts.isManuallyZoomed() == false ) {
      highCharts.moveLiveWindow();
      highCharts.getPercentile().zoomLastPoint();
      structureRedraw();
    }
  }
  if (loading) {
    //highCharts.setSeriesVisibility(false);
    //lastLoadingCount += index;
    highCharts.showLoading("Loading Data... " + lastLoadingCount + " points processed.");
  }
}

function processStart() {
  loading = true;
  if (highCharts) {
    highCharts.options.legend.enabled = false;
    highCharts.legend.display = false;
    highCharts.showLoading("Started Loading Data...");
  }  
  lastLoadingCount = 0;
  historicalStartTime = Date.now();
  clearOn = false;
}

function processEnd( sampled ) {
  //end marker
  if (highCharts) {
    highCharts.onHistoricalEnd();
    if ( sampled ) {
      console.info( "Chart data is sampled." );
      highCharts.setSampled( true );
    }
  }
  createMouseWatchDogTimer();
  loading = false;
  processConfigOn = false;
  console.info("Processed " + messageCount + " messages. Elapsed " + ( Date.now() - historicalStartTime + "ms" ));
}

function structureRedraw(current) {
  if (loading || clearOn) {
    console.log("Ignore structure redraw..  Loading/Clear in progress..")
    return;
  }
  if (highCharts.legend) {
    highCharts.legend.reformat(current);
  }
  highCharts.redraw();
}

function resetMouseWatchDogTimer() {
  if (_mouseWatchDogTimer) {
    window.clearTimeout(_mouseWatchDogTimer);
  }
  createMouseWatchDogTimer();
}

var refreshInterval = setInterval(redraw, 1000);

function removeId(id) {
  var elem = document.getElementById(id);
  if (elem) {
    elem.parentNode.removeChild(elem);
  }
}

var source;

function getWebsocketURL() {
  var websocketHost = "127.0.0.1";
  var websocketPort = "10080";
  var websocketPath = "range";
  var processId = "0";
  var protocol = "ws";

  if (getParameterByName("host")) {
    websocketHost = getParameterByName("host");
  }
  if(getParameterByName("configSource") == "PTW" || getParameterByName("configSource") == "Mobile"){
    return null;
  }
  if (getParameterByName("port")) {
    websocketPort = getParameterByName("port");
  }
  var urlPath = getParameterByName("path");
  if ( urlPath == "/")  {
    websocketPath = "";
  } else if ( urlPath ) {
    websocketPath = getParameterByName("path");
  }
  if (getParameterByName("processId")) {
    processId = getParameterByName("processId");
  }
  if (getParameterByName("protocol")) {
    protocol = getParameterByName("protocol");
  }
  if (getParameterByName("source")) {
    source = getParameterByName("source");
  }

  var url = protocol + "://" + websocketHost + ":" + websocketPort + "/" + encodeURIComponent(websocketPath);
  url = url + window.location.search;
  if (url.indexOf('?') < 0) url = url + "?1=1";
  return url;
}

var wsUri = getWebsocketURL();
var rangeSocket;
var hearbeatTimer;

if ( !disconnected && isValued(wsUri)  ) {
  console.info("Connecting to socket: " + wsUri);
  rangeSocket = new WebSocket(wsUri);
  rangeSocket.onopen = function(event) {
    console.log("Range Socket opened");
  };
  rangeSocket.onclose = function(event) {
    console.log("Range Socket closed", event);
    if (highCharts) {
      if (chartSocket!=null && rangeSocket != chartSocket){
        highCharts.setConnectionStatus("partialDisconnect");
        highCharts.showConnectionStatus();
      }else{
        chartSocket = null;
        if(hearbeatTimer != null){
          clearInterval (hearbeatTimer);
        }
        highCharts.setConnectionStatus("disconnect");
        highCharts.showConnectionStatus();
        reconnect(wsUri, null, null, null);
      }
    }

  };
  rangeSocket.onerror = function(event) {
    console.log("Errorin range socket: " , event);
  };
  rangeSocket.onmessage = onMessage;
}

function checkHeartbeat () {
  if(new Date() - lastHearbeatTime > HEARTBEAT_INTERVAL * MAX_MISSED_HEARTBEAT){
    console.log("Missing multiple heartbeats. Will automatically reconnect.");
    if(chartSocket != null){
      chartSocket.close();
      chartSocket = null;
    }
    clearInterval (hearbeatTimer);
  }
}


var chartSocket;

function onMessage(event) {
  try{
    chartQueue.push(JSON.parse(event.data));
    messageCount++;
    totalBytes += event.data.length;
  } catch( e ) {
    console.warn( "Bad Message ", event.data);
  }

}

function getSeries(seriesName) {
  if (highCharts.series) {
    for (var index = 0; index < highCharts.series.length; index++) {
      if (highCharts.series[index].name == seriesName) return highCharts.series[index];
    }

  }
  return null;
}

function findSeriesIndex(seriesName) {
  if (highCharts == null) {
    return -1;
  }
  if (highCharts.series) {
    for (var index = 0; index < highCharts.series.length; index++) {
      if (highCharts.series[index].name == seriesName) return index;
    }

  }
  return -1;
}

function indexSeries(series) {
  var pointIndex = {};
  if (!series || !series.xData || series.xData.length == 0) return pointIndex;

  for (var index = 0; index < series.xData.length; index++) {
    if (index >= series.data.length) continue;
    pointIndex[series.xData[index]] = index;
  }

  return pointIndex;
}

function handleSeriesData(seriesPointCache, seriesData) {
 if (clearOn) {
   return;
  }
  var series = null;
  var seriesIndex = findSeriesIndex(seriesData.seriesName);
  var seriesPoints = {};
  if (seriesIndex < 0) {
    addSeries(seriesData);
    seriesIndex = findSeriesIndex(seriesData.seriesName);
  }
  series = highCharts.series[seriesIndex];    
    if (seriesIndex in seriesPointCache) {
      seriesPoints = seriesPointCache[seriesIndex];
    } else {
      seriesPoints = indexSeries(series);
      seriesPointCache[seriesIndex] = seriesPoints;
    }
    
    var newData = series.chart.convertData( series, seriesData );
    if (newData && newData.length > 0) {
      for (var index = 0; index < newData.length; index++) {
        var newPoint = newData[index];
        var newX = newPoint[0];
        var newY = newPoint[1];
        var cachedIndex = null;
        if (newX in seriesPoints) {
          cachedIndex = seriesPoints[newX];
          series.yData[cachedIndex] = newY;
          repeatedX++;
        } else {
      if (loading) {
        if (!isValued(series._data)) {
        series._data = [];
        }
        series._data.push(newPoint);
        cachedIndex = series._data.length - 1;
        lastLoadingCount++;
      } else {
        series.addPoint(newPoint, false, false, false);
        cachedIndex = series.yData.length - 1;
      }
        }
        seriesPoints[newX] = cachedIndex;
      }
    }
}

function commandClear(command) {
  if (clearOn) {
    console.info("Active clear/refresh command... Ignore clear command.");
    return;
  }
  console.info("Processing Clear");
  clearOn = true;
  if (command != null && chartSocket != null && chartSocket.readyState == 1) {
    var cmd = JSON.stringify(command);
    chartSocket.send(cmd);
  }

  if (highCharts != null) {
    highCharts.clearAllSeries();
  }
  messageCount = 0;
}

function commandRefresh(command) {
  console.info("Processing Refresh");
  clearOn = true;
  doReload();
}

function handlePublish( json ) {
  sendSocketMessage( chartSocket, json );
}

function processConfig(command) {
  if (!isValued(command)) {
    return;
  }
  var title = getParameterByName( "path" );
  if ( isValued( command.title ) ) {
    title = command.title;
  }


  logger.log("Processing Config: ", title, command);

  if (processConfigOn) {
      console.log("Config command active.  Ignore..");
      return;
  }
  
  processConfigOn = true;
  if (graphInfo.graphConfig) {
      command = MergeRecursive(graphInfo.graphConfig, command)
  } else {
      graphInfo.graphConfig = command;
  }
  if ( isDefined( this.saveGraphConfig ) ) {
    saveGraphConfig(command.title, command);
  }
  var frameColor = getConfigParameter(KEY_FRAME_COLOR);
  if (frameColor) {
    document.getElementById("highChartsContainer").style.borderColor = frameColor;
  }
  
  if (!isValued(highCharts)) {
  highCharts = recreateGraph(command);
  highCharts.addChartPublisher(  handlePublish );
  }
  if (command.configSource == null || command.configSource == "Excel" || command.configSource == "PTW" || command.configSource == "Mobile") {
    requestData(command);
  }
}

function processHeartbeat(){
  lastHearbeatTime = new Date();
}

function handleCommands(command) {
  console.log("Received command: ", command.command, command);
  var borderEnabled = isBorderEnabled();
  command.isBorderEnabled = function () {
    return borderEnabled;
  }

  switch (command.command) {
    case "refresh":
      commandRefresh(command);
      break;
    case "clear":
      commandClear(command);
      break;
    case "config":
      processConfig(command);
      break;
    case "parameters":
      handleParameterUpdate(command);
      break;
    case "historicalStart":
      processStart();
      break;
    case "historicalEnd":
      processEnd( command.sampled );
      break;
    case "heartbeat":
      processHeartbeat();
      break;
    default:
      console.log("Received unhandled command", command);
      break;
  }
}


function createMouseWatchDogTimer() {
  var delay = getConfigParameter(KEY_MOUSE_INACTIVE_TIMER);
  if (!delay) {
    delay = defaultOptions[ KEY_MOUSE_INACTIVE_TIMER ];
  }
  delay *= 1000;
  _mouseWatchDogTimer = window.setTimeout(function() {
    structureRedraw(true);
    console.log("No mouse events..  set the last price in legend.");
  }, delay);
}


function configUpdate( command ) {
  if(!isValued(command)){
    return;
  }
  logger.info( "Updating: ", command );
  sendClear();
  requestData( command );
}

/**
 * Sends a clear command on the chartSocket
 */
function sendClear() {
  console.info( "****Sending Clear command");
  var command = {
    type : "command",
    command: "clear"
  };
  commandClear(command);
}

function massageURLS( command ) {
  var requestURLS = getParameterByName( "urls" );
    if ( typeof requestURLS == "string" && requestURLS.length > 0 ) {
      var newURLS = requestURLS.split( "," );
      for ( var index = 0; index < newURLS.length; index++ ) {
        newURLS[ index ] = newURLS[ index ].trim();
      }
      command.urls = newURLS;
    }
}



function requestData(command) {
  if ( disconnected ) {
    logger.info( "Disconnected state, no data request.");
    return;
  }
  massageURLS( command );
  var urls = shuffle(command.urls);
  var websocketHost = command.host;
  var websocketPort = command.port;
  if (websocketHost == null && isValued(command.parameters)) {
    websocketHost = command.parameters.host;
    websocketPort = command.parameters.port;
  }
  if (!isValued( urls) && ( !isValued(websocketHost) || !isValued(websocketPort) ) ) {
    var testingFlag = getParameterByName( "testing" );
    if ( isValued( testingFlag ) && testingFlag.toLowerCase() == "true") {
      logger.info("Only range socket exists, command socket will be rangesocket" );
      chartSocket = rangeSocket;
    } else {
      logger.info( "Waiting for configuration command" );
      processConfigOn = false;
      return;
    }
  } else if (chartSocket == rangeSocket) {
    chartSocket = null;
  }
  logger.info( "Requesting Chart Data from ViewServer", urls );  
  connect(urls, websocketHost, websocketPort, command, false);
}

function randomIntFromInterval(min, max) {
  return Math.floor(Math.random() * (max - min + 1) + min);
}

function shuffle(array) {
  if (!isValued(array)) {
    return null;
  }
  var currentIndex = array.length, temporaryValue, randomIndex;

  // While there remain elements to shuffle...
  while (0 !== currentIndex) {

    // Pick a remaining element...
    randomIndex = Math.floor(Math.random() * currentIndex);
    currentIndex -= 1;

    // And swap it with the current element.
    temporaryValue = array[currentIndex];
    array[currentIndex] = array[randomIndex];
    array[randomIndex] = temporaryValue;
  }

  return array;
}


var reconnectTimeout = 2000;
var retryCount = 1;

function reconnect(urls, websocketHost, websocketPort, command) {
  if (retryCount == 0){
  // Adding a cool down period on before reconnecting after disconnect
  var coolDownTime = randomIntFromInterval(1, 10) * 1000;
  console.log ("Cool down ... Waiting for " + coolDownTime/1000 + " seconds before attempting to reconnect");
  setTimeout (reconnect, coolDownTime, urls, websocketHost, websocketPort, command, true);
  retryCount++;
  }else{
  console.log("Reconnecting ... retry # " + retryCount + " in " + reconnectTimeout / 1000 + " seconds");
  setTimeout(connect, reconnectTimeout, urls, websocketHost, websocketPort, command, true);
  retryCount++;
  if (reconnectTimeout < MAX_RECONNECT_TIMEOUT) {
    reconnectTimeout *= 2;
  } else {
    reconnectTimeout = 2000;
  }
  }
}

function disconnect() {
  console.log("Closing Sockets.")
  if (rangeSocket) {
    rangeSocket.close();
    delete rangeSocket;
    rangeSocket = null;
  }
  if (chartSocket) {
    chartSocket.close();
    delete chartSocket;
    chartSocket = null;
  }
}

function connect(urls, websocketHost, websocketPort, command, isReconnect) {
  if(command != null) {
    var cmd = JSON.stringify(command);
  }

  if (chartSocket == null) {
    if (urls != null && urls.length > 0) {
      if(cmd != null) {
        var index = retryCount % urls.length;
        var url = "ws://" + urls[index] + "/";
      }else{
        var url = urls;
      }
    }
    logger.info( "URLS: ", url );
    if (!isValued(url)) {
      if (!isValued(websocketHost) || !isValued(websocketPort)) {
        logger.log("Missing host/port");
        return;
      }
      url = "ws://" + websocketHost + ":" + websocketPort + "/";
    }
    url = url +  window.location.search;
    console.log("Requesting data from ChartingViewServer on ", url);

    chartSocket = new WebSocket(url);
    chartSocket.onopen = function(event) {
      if (isReconnect) {
        retryCount = 0;
        reconnectTimeout = 2000;
      }
      console.log("Chart Socket opened");
      if (highCharts) {
        if(isReconnect){
          clearOn = true;
          highCharts.clearAllSeries();
          messageCount = 0;
        }
        highCharts.setConnectionStatus("connected");
        highCharts.showConnectionStatus();
      }
      if(cmd !=null ) {
        chartSocket.send(cmd);
      } 
    };
    chartSocket.onclose = function(event) {
      console.log("Chart Socket closed", event);
      if (highCharts) {
        highCharts.setConnectionStatus("disconnect");
        highCharts.showConnectionStatus();
      }
      chartSocket = null;
      if(hearbeatTimer != null){
        clearInterval (hearbeatTimer);
      }
      reconnect(urls, websocketHost, websocketPort, command);
    };
    chartSocket.onerror = function(event) {
      console.log("Unable to connect to websocket server");
    };
    chartSocket.onmessage = onMessage;
  } else {
    if (chartSocket.readyState == chartSocket.OPEN && cmd != null) {
      sendSocketMessage( chartSocket, cmd );
    } else {
      console.debug("Chart socket is not open");
    }
  }
}

function sendSocketMessage( socket, message ) {
  if (socket != null && socket.readyState == socket.OPEN && message != null) {
      socket.send(message);
    } else {
      console.debug("Chart socket is not open");
    }  
}

function isParameterChanged(previousOptions, currentOptions, parameterKey) {
  if (highCharts && highCharts.options) {
    if (previousOptions[parameterKey] != currentOptions[parameterKey]) {
      return true;
    }
  }

  return false;
}

function handleParameterUpdate(command) {
  console.log("Received Parameter Update: ", command.parameters);
  if (graphInfo.graphConfig) {
      command = MergeRecursive(graphInfo.graphConfig, {"parameters": command.parameters})
  } else {
      graphInfo.graphConfig = command;
  }    
  saveGraphConfig(command.title, command);
  if (highCharts && highCharts.options) {
    var previousOptions = highCharts.options.mergedOptions;
    var previousXZoom = highCharts.getOptionParameter(KEY_STATIC_START_OFFSET);

    if (highCharts.options.pilotConfig) highCharts.options.pilotConfig.parameters = command.parameters;
    highCharts.options.mergedOptions = mergeOptions(command.parameters);

    var x = getConfigParameter("chart.xpos");
    var y = getConfigParameter("chart.ypos");
    var h = getConfigParameter("chart.height");
    var w = getConfigParameter("chart.width");
    if (x && y && h && w) {
      setBounds({"x":parseInt(x), "y":parseInt(y), "width":parseInt(w), "height":parseInt(h)});
    }
    
    var currentZoom = highCharts.getOptionParameter(KEY_STATIC_START_OFFSET);
    if (previousXZoom != currentZoom) {
      highCharts.setOptionXAxisZoom();
    }
    if (isParameterChanged(previousOptions, highCharts.options.mergedOptions, KEY_ROLLING_WINDOW)) setConfigRollingWindow(highCharts);
    highCharts.recalculateParameters( previousOptions, highCharts.options.mergedOptions );
  }
}

function printArray(array) {
  for (x in array) {
    if (array[x] instanceof Array) {
      array[x] = "[" + array[x].join(",") + "]";
    }
  }
  return "[" + array.join(",") + "]";
}

function calcSpreadMid(seriesData) {
  var convertedData = [];
  for (var index = 0; index < seriesData.data.length; ++index) {
    var point = seriesData.data[index];
    if (point.bid == null || point.offer == null) {
      convertedData.push([point.x, null]);
    } else {
      convertedData.push([
        point.x,
        point.bid + (point.offer - point.bid) / 2,
      ]);
    }
  }
  return convertedData;
}

function getOwnAxisOrientation() {
  var allSeries = getAllSeries(highCharts);
  var eventualSeriesCount = allSeries.length + 1;
  if (eventualSeriesCount % 2 == 0) return false;
  return true;
}

function addSeries(seriesData) {
  console.debug(seriesData);
  var yaxis = null;
  var yAxisName = seriesData.yAxisLabel;
  var globalOwnAxis = getBoolean(getConfigParameter("chart.ownaxis")) || this.highCharts.axisType == AXIS_TYPE_OWN;
  var newAxisOptions = {};


  var enableTracking = true;
  if (highCharts.options.performance != null) enableTracking = highCharts.options.performance.seriesTooltips;
  
  var dataGroupingEnabled = highCharts.getOptionParameterBoolean( KEY_DATAGROUPING_ENABLED );
  var dataGroupingWidth = highCharts.getOptionParameter(KEY_DATAGROUPING_WIDTH);
  var visible = isValued( seriesData.hidden ) == false || seriesData.hidden == false;
  var showInLegend = visible || isValued( seriesData.errorMessage );

  var seriesOptions = {
    "name": seriesData.seriesName,
    "id": seriesData.seriesName,
    "enableMouseTracking": enableTracking,
    "lineWidth": 1.5,
    "type" : "line",
    "visible" : visible,
    "dataGrouping": {
      "enabled": dataGroupingEnabled,
      "groupPixelWidth" : dataGroupingWidth,
      "dateTimeLabelFormats": dataGroupingTimeFormats
    },
    //"data": loadData(seriesData)
  };
  
  if(seriesData.chartType == "Line"){
  seriesOptions.step = "left";
  }
  if ( seriesData.chartType == "OHLC" || seriesData.chartType == "Candlestick" ) {
    seriesOptions.type = seriesData.chartType.toLowerCase();
    seriesOptions.dataGrouping.groupPixelWidth = 20;
    // seriesOptions.dataGrouping.enabled = false;
    seriesOptions.pointWidth = 5;
  }
  if ( seriesData.errorMessage ) {
    //chances are there will be no data in error message series...turn off datagrouping
    seriesOptions.errorMessage = seriesData.errorMessage;
    seriesOptions.dataGrouping.enabled = false;
  }




  if ( seriesData.linkedSeriesName ) {
    logger.info( "Linked series: ", seriesData.seriesName, " to ", seriesData.linkedSeriesName );
    seriesOptions.linkedTo = seriesData.linkedSeriesName;
    seriesOptions.showInLegend = false;
    var linkedSeries = highCharts.getSeriesByName( seriesData.linkedSeriesName );
    if ( linkedSeries != null ) {
      seriesOptions.color = linkedSeries.color;
      yaxis = linkedSeries.yAxis.options.id;
    }
  }

 if (!seriesData.errorMessage && !yaxis && globalOwnAxis && getAllSeries(highCharts).length > 0) {
    yAxisName = seriesData.seriesName;
    newAxisOptions.left = getOwnAxisOrientation();
    if (yAxisName) {
      yaxis = assignYAxis(yAxisName, newAxisOptions);
    }
  }


  if (yaxis) {
    seriesOptions.yAxis = yaxis;
  }
  console.info("New Series ", seriesOptions);

  var series = highCharts.addSeries(seriesOptions, false);

  if (seriesData.chartType == "Spread") {
    // Add midpoint series.
    highCharts.addSeries({
      name: seriesData.seriesName + "-MID",
      "id": seriesData.seriesName,
      "type": "spline",
      "enableMouseTracking": true,
      "zIndex": 1,
      "color": "white",
      "data": calcSpreadMid(seriesData),
      "yAxis": yaxis
    }, false);

  }

  updateAxis(seriesData);
  highCharts.colorYAxis();
}

function getYAxis(label) {
  if (highCharts && highCharts.yAxis) {
    for (var index = 0; index < highCharts.yAxis.length; index++) {
      if (highCharts.yAxis[index].userOptions.id == label) {
        return highCharts.yAxis[index];
      }
    }
  }
  return null;
}

function getOption(options, property, defaultValue) {
  return typeof options !== 'undefined' && options != null && options.hasOwnProperty(property) ? options[property] : defaultValue;
}



/**
 * We only support two main panes, at 70% and 20% heights respectively
 * In highcharts it's controlled by yaxis heights
 */
function redistributePanes() {
  var maxPaneIndex = 0;
  for (var index = 0; index < highCharts.yAxis.length; index++) {
    var axis = highCharts.yAxis[index];
    if (axis.options.paneIndex > maxPaneIndex) maxPaneIndex = axis.options.paneIndex;
  }
  for (var index = 0; index < highCharts.yAxis.length; index++) {
    var axis = highCharts.yAxis[index];
    //highstock default axis
    if (axis.options.id == "navigator-y-axis") continue;
    var paneIndex = axis.options.paneIndex;
    if (typeof paneIndex == 'undefined' || paneIndex == 0) {
      var updatedOptions = { height: "100%" };
      if (maxPaneIndex == 1) {
        updatedOptions.height = "70%"
      }
      axis.update(updatedOptions, false);
    }
  }
}


function getYAxisId(seriesName) {
  return "y" + seriesName;
}

function updateAxis(seriesData) {
  if (highCharts.xAxis.length == 0) return;

  var seriesNumber = seriesData.seriesNumber;
  var xaxis = null;
  if (highCharts.xAxis.length >= 1) {
    xaxis = highCharts.xAxis[0];
  }
  //have to figure out multiple xaxis->yaxis charts in the future
  if (seriesData.timeSeries == false && xaxis.options.type == "datetime") {
    console.debug("Switching to linear: current type %s", xaxis.options.type);
    xaxis.update({ "type": "linear" });
  }
}

function isYAxisShared(id) {
  var allSeries = getAllSeries(highCharts);
  var matchCount = 0;
  for (var index = 0; index < allSeries.length; index++) {
    var otherSeries = allSeries[index];
    var otherYAxis = otherSeries.yAxis;
    if (otherYAxis && otherYAxis.options.id == id) {
      matchCount++;
    }
  }

  return matchCount > 1;
}

function removeYAxis(id, ignoredSeries) {
  var inUse = false;
  for (var index = 0; index < highCharts.series.length; index++) {
    var otherSeries = highCharts.series[index];
    var otherYAxis = highCharts.get(otherSeries.yAxis);

    if (otherYAxis && otherYAxis.options.id == id && otherSeries != ignoredSeries) {
      inUse = true;
      break;
    }
  }
  if (!inUse) {
    var axis = highCharts.get(id);
    //only destroy the non default axis
    if (axis.userOptions.index > 0) {
      axis.update({
        title: {
          text: null
        }
      });
      axis.remove();
      redistributePanes();
    }
    recalculateYAxisOffsets();
    highCharts.reflow();
  }
}

function removeSeries(seriesIndex) {
  if (seriesIndex > 0) {
    console.log("Removing indicator series: " + highCharts.series[seriesIndex].name);
    var series = highCharts.series[seriesIndex];

    if (series.yAxis) {
      var oldId = series.yAxis.options.id;
      series.update({
        yAxis: 0
      });
      if (oldId) {
        removeYAxis(oldId);
      }
    }

    series.remove(false);
    structureRedraw(true);
  }

}

function getAllSeries(chart) {
  return chart.getAllSeries();
}


function toggleLegend() {
  for (i in highCharts.series) {
    chart.series[i].update({ showInLegend: highCharts.series[i].options.showInLegend == null ? false : !highCharts.series[i].options.showInLegend }, false);
  }

  chart.redraw();
}

function debugToggle() {
  pilotGraphDebug = !pilotGraphDebug;

  var targetDisplay = "none";
  if (pilotGraphDebug) targetDisplay = "block";
  $("#graphDebug").css({ display: targetDisplay });
}



function isBorderEnabled() {
  var removeBorders = getParameterByName( "removeBorders" );
  return isUndefined( removeBorders ) || removeBorders != "true";
}

/*
 * Initial function that is called when chart loads. If you want the chart to default to a security, or if you're passing a security in with a query string then load it here
*/
function displayChart() {
  var removeBorders = isBorderEnabled();
  if ( isBorderEnabled() == false ) {
      $('#highChartsContainer').css("border", "none"); 
  }
}

/*
* Recursively merge properties of two objects 
*/
MergeRecursive = function (src, dst) {

  for (var p in dst) {
    try {
      // Property in destination object set; update its value.
      if ( dst[p].constructor==Object ) {
        src[p] = MergeRecursive(src[p], dst[p]);

      } else {
        src[p] = dst[p];

      }

    } catch(e) {
      // Property in destination object not set; create it and set its value.
      src[p] = dst[p];

    }
  }

  return src;
}



String.prototype.format = String.prototype.f = function() {
  var s = this,
    i = arguments.length;

  while (i--) {
    s = s.replace(new RegExp('\\{' + i + '\\}', 'gm'), arguments[i]);
  }
  return s;
};

function requestMoreData( requestedStart ) {
  if ( !graphInfo || !graphInfo.graphConfig ) {
    return;
  }
  var requestAgain = false;
  if( graphInfo.graphConfig.rangeConfig ) {
    if ( requestedStart < graphInfo.graphConfig.rangeConfig.startTime ) {
        logger.info( "Requesting additional data starting at: ", new Date( requestedStart ) );
        graphInfo.graphConfig.rangeConfig.startTime = requestedStart;
        requestAgain = true;
    }
  }
  if ( graphInfo.graphConfig.queryConfig ) {
    var requestedDate = new Date( requestedStart );

    graphInfo.graphConfig.queryConfig.forEach( function( config ){
      if ( !config.startDate || config.startDate.length != 10 || config.startDate.charAt( 4 ) != '-' ||  config.startDate.charAt( 7 ) != '-' ) return;
      var split = config.startDate.split( '-' );

      var startDate = new Date( parseInt( split[ 0 ] ), parseInt( split[ 1 ] ) - 1, parseInt( split[ 2 ] ) );
      startDate.setHours(0,0,0,0);
      if ( requestedDate.getTime() < startDate.getTime() ) {
        var year = requestedDate.getFullYear();
        var month = STRING_UTILS.leftPad( ( requestedDate.getMonth() + 1 ) + "", "0", 2 );
        var date = STRING_UTILS.leftPad( ( requestedDate.getDate() ) + "", "0", 2 );
        config.startDate = year + "-" + month + "-" + date;
        logger.info( "Requesting additional data starting at: ", config.startDate );
        
        requestAgain = true;
      }

    });
    if ( requestAgain ) {
      requestDataAgain();        
    }
    
  }

}

function requestDataAgain( ) {
  clearOn = false;
  graphInfo.graphConfig.configSource = null;  
  processConfig( graphInfo.graphConfig );
  highCharts.clearAllSeries();
}
